package sk.ygor.raynor.rebind.reflection

import com.google.gwt.core.ext.{GeneratorContext, TreeLogger, Generator}
import java.lang.String
import com.google.gwt.user.rebind.{SourceWriter, ClassSourceFileComposerFactory}
import sk.ygor.xtemplate.XTemplate
import sk.ygor.raynor.rebind.serializable._
import com.google.gwt.core.ext.typeinfo.{JClassType, JType}
import sk.ygor.raynor.client.service.JSONService
import sk.ygor.raynor.rebind.service.JSONServiceAnalysis
import sk.ygor.xtemplate.ScalaXTemplate
import sk.ygor.raynor.rebind.common.{ScalaGeneratorWorker}
import sk.ygor.raynor.client.json.JSONSerializer
import sk.ygor.raynor.client.reflection.{JSONStructure, JSONServiceInfo, JSONTypeInfo}

class JSONStructureGenerator extends Generator {
  def generate(logger: TreeLogger, context: GeneratorContext, typeName: String) =
    new JSONStructureGeneratorWorker(logger, context, typeName).generate
}

class JSONStructureGeneratorWorker(logger: TreeLogger, context: GeneratorContext, typeName: String)
        extends ScalaGeneratorWorker(logger, context, typeName) with JSONServiceAnalysis {
  def generateHeader(composer: ClassSourceFileComposerFactory) {
    composer addImplementedInterface inputClassType.getParameterizedQualifiedSourceName
    composer addImports (
            classOf[JSONTypeInfo],
            classOf[JSONStructure],
//            classOf[JSONStructure.Payload],
            classOf[JSONTypeInfo.BaseType],
            classOf[JSONTypeInfo.CollectionType],
            classOf[JSONTypeInfo.GenericType],
            classOf[JSONTypeInfo.GenericType.Attribute],
            classOf[JSONTypeInfo.GenericType.NoAttributes],
            classOf[JSONTypeInfo.GenericType.HasAttributes],
            classOf[JSONServiceInfo],
            classOf[JSONServiceInfo.ServiceMethod],
            classOf[JSONServiceInfo.ServiceMethod.Parameter],
            classOf[JSONSerializer.ParametrizedClassName],
            classOf[java.util.Map[_, _]],
            classOf[java.util.HashMap[_, _]]
            )
  }

  class Template extends ScalaXTemplate {
    val serviceRef = parameter[String]
    val serviceInfo = parameter[String]
    val typeRef = parameter[String]
    val typeInfo = parameter[String]

    val addService = "services.put(\"" ~ serviceRef ~ "\", " ~ serviceInfo ~ ");\n"
    val addType = "types.put(\"" ~ typeRef ~ "\", " ~ typeInfo ~ ");\n"

    val rootBlock = """

        private HashMap<String, JSONServiceInfo> services = new HashMap<String, JSONServiceInfo>();
        {
         """ ~ addService ~ """
        }

        private HashMap<String, JSONTypeInfo> types = new HashMap<String, JSONTypeInfo>();
        {
          """ ~ addType ~ """
        }

        public Map<String, JSONServiceInfo> services() {
          return services;
        }

        public Map<String, JSONTypeInfo> types() {
          return types;
        }

    """
  }

  def generateBody(sourceWriter: SourceWriter) {
    val xtpl = new Template
    val (analyzedServices, analyzedTypes) = analyze
    for ((serviceType, serviceMethods) <- analyzedServices) {
      xtpl.serviceRef.value = serviceType.getParameterizedQualifiedSourceName
      xtpl.serviceInfo.value = serviceInfoSourceCode(serviceType, serviceMethods)
      xtpl.addService.parse
    }
    for ((typeType, typeInfo) <- analyzedTypes) {
      xtpl.typeRef.value = typeType.getParameterizedQualifiedSourceName
      xtpl.typeInfo.value = typeInfoSourceCode(typeInfo)
      xtpl.addType.parse
    }
    xtpl.rootBlock.parse
    sourceWriter println xtpl.rootBlock.getText
  }

  private def typeInfoSourceCode(typeInfo: StructureInfo) = "\n" + (typeInfo match {
    case BaseTypeStructureInfo(nature, _) => "new BaseType(" +
            "BaseType.Nature." + nature.name +
            ")"
    case c@CollectionTypeStructureInfo(nature, tail) => "new CollectionType(" +
            "CollectionType.Nature." + nature.name + "," +
            withDoubleQuotes(tail.getParameterizedQualifiedSourceName) +
            ")"

    case g@GenericTypeStructure(_, properties, subTypes) => "new GenericType(" +
            parametrizedClassNameSourceCode(g.parametrizedClassName) + "," +
            (properties match {
              case Left(reason) => "\nnew NoAttributes(" +
                      withDoubleQuotes(reason) +
                      ")"
              case Right(propertiesMap) => "\nnew HasAttributes(new Attribute[] {" +
                      (for ((property, jType) <- propertiesMap) yield
                        ("\nnew Attribute(" +
                                withDoubleQuotes(property.jsonFieldName) + "," +
                                withDoubleQuotes(jType.getParameterizedQualifiedSourceName) +
                                ")")
                              ).mkString(",") +
                      "})"
            }) + "," +
            "\nnew String[]{" +
            subTypes.map(t => withDoubleQuotes(t.getParameterizedQualifiedSourceName)).mkString(",") +
            "}" +
            ")"
  })

  private def serviceInfoSourceCode(serviceType: JClassType, serviceMethods: List[ServiceMethod]) =
    "\nnew JSONServiceInfo(" +
            withDoubleQuotes(serviceType.getPackage.getName) + "," +
            withDoubleQuotes(serviceType.getSourceNameWithoutPackage) + "," +
            "\nnew ServiceMethod[]{" +
            serviceMethods.map(serviceMethodSourceCode(_)).mkString(",") +
            "})"


  private def serviceMethodSourceCode(serviceMethod: ServiceMethod) = "\nnew ServiceMethod(" +
          withDoubleQuotes(serviceMethod.name) + "," +
          withDoubleQuotes(serviceMethod.returnType.getParameterizedQualifiedSourceName) + "," +
          "\nnew Parameter[] {" + serviceMethod.parameterTypes.map(parameterTypeSourceCode(_)).mkString(",") + "}" +
          ")"

  private def parameterTypeSourceCode(parameter: Tuple2[String, JType]) = parameter match {
    case (parameterName, parameterType) => "\nnew Parameter(" +
            withDoubleQuotes(parameterName) + "," +
            withDoubleQuotes(parameterType.getParameterizedQualifiedSourceName) +
            ")"
  }

  def analyze = {
    val analyzedServices = context.getTypeOracle.getType(classOf[JSONService].getName).
            getSubtypes.
            filter(t => !t.maybeInterface.isEmpty).
            map(t => (t, analyzeServiceMethods(t))).
            toList
    val analyzedTypes = collectReachableTypes(
      analyzedServices.flatMap(s => s._2).toList.flatMap(serviceMethod => {
        (serviceMethod.returnType :: serviceMethod.parameterTypes.map(p => p._2))
      }))
    (analyzedServices, analyzedTypes)
  }

  def collectReachableTypes(initialList: List[JType]): Map[JType, StructureInfo] = {
    def collect(todo: List[JType], buffer: scala.collection.mutable.Map[JType, StructureInfo]): Map[JType, StructureInfo] = {
      if (todo.forall(x => buffer.contains(x))) {
        Map() ++ buffer
      } else {
        todo.foreach(t => {
          buffer.getOrElseUpdate(t, {
            JSONSerializableAnalysis.getStructureInfo(t)
          })
        })
        val allDependend = List.flatten(buffer.values.map(strInf => strInf.dependendTypes).toList)
        collect(allDependend, buffer)
      }
    }
    collect(initialList, scala.collection.mutable.Map.empty[JType, StructureInfo])
  }

}
